﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.ComponentModel;
using System.IO;
using Microsoft.Win32;
using MTS;

namespace MTSGui
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private WorldRenderer renderer;

        private BackgroundWorker renderWorker;

        private DateTime startTime;

        private string saveDir;

        private string progressTextFormat = "{0}";
        private string titleTextFormat;
        private string titleTextFormatRendered;

        private string worldPath;
        private string worldName;

        public MainWindow()
        {
            InitializeComponent();

            StatsToggle.IsChecked = Properties.Settings.Default.ReportToRI;

            UpdateInfo Info = UpdateCheck.CheckForUpdate();
            if (Info.UpdateAvailable)
            {
                new Update(Info).ShowDialog();
            }

            titleTextFormat = this.Title + (Environment.Is64BitProcess ? " (64-bit)" : "") + " - {0}";

            this.Title = this.Title + (Environment.Is64BitProcess ? " (64-bit)" : "");

            saveDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraft\\saves");
            if (Directory.Exists(saveDir))
            {
                //Search for saves and add them to the render dropdown
                foreach (string SaveFolder in Directory.GetDirectories(saveDir))
                {
                    if (!File.Exists(Path.Combine(SaveFolder, "level.dat")))
                        continue; //Not a world folder

                    MenuItem NewItem = new MenuItem();

                    //Some trickery to turn "World1" into "World 1"
                    string WorldName = SaveFolder.Substring(SaveFolder.LastIndexOf('\\') + 1);
                    if (WorldName.StartsWith("World") && WorldName.Length == 6)
                        WorldName = WorldName.Insert(5, " ");

                    NewItem.Header = WorldName;
                    NewItem.Tag = SaveFolder;
                    NewItem.Click += new RoutedEventHandler(RenderFromSave);
                    RenderMenu.Items.Add(NewItem);
                }
                if (RenderMenu.Items.Count > 0)
                    RenderMenu.Items.Add(new Separator());
            }

            MenuItem RenderFileMenuItem = new MenuItem { Header = "Open world..." };
            RenderFileMenuItem.Click += new RoutedEventHandler(RenderFromPath);
            RenderMenu.Items.Add(RenderFileMenuItem);

            SetMenuState(MenuMode.NoRender);
            RenderInfo.Visibility = Visibility.Hidden;

            UpdateTypeButton();
            TaskbarItemInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal;
            renderer = new WorldRenderer();

            renderWorker = new BackgroundWorker();
            renderWorker.WorkerSupportsCancellation = false;
            renderWorker.WorkerReportsProgress = true;
            renderWorker.DoWork += new DoWorkEventHandler(RendererWork);
            renderWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RendererComplete);
            renderWorker.ProgressChanged += new ProgressChangedEventHandler(RendererProgress);
        }

        private void UpdateTypeButton()
        {
            ImageType t = (ImageType)System.Enum.Parse(typeof(ImageType), Properties.Settings.Default.RenderMode);
            switch (t)
            {
                case ImageType.TERRAIN:
                    MapTypeButton.Text = "Terrain";
                    break;
                case ImageType.OBLIQUE:
                    MapTypeButton.Text = "Oblique (Light: " + (((int)Properties.Settings.Default.LightLevel) + 1) + ")";
                    break;
                case ImageType.ISOMETRIC:
                    MapTypeButton.Text = "Isometric";
                    break;
                case ImageType.CAVEMAP:
                    MapTypeButton.Text = "Cave";
                    break;
                case ImageType.HEIGHTMAP:
                    MapTypeButton.Text = "Heightmap";
                    break;
                case ImageType.SPECTROGRAPH:
                    BlockColors bc = new BlockColors();
                    MapTypeButton.Text = "Spectrograph (" + bc.Blocks[Properties.Settings.Default.spectrographBlock].Name + ")"; //TODO: Display appropriate type
                    bc = null;
                    break;
                default:
                    MapTypeButton.Text = "Unknown";
                    break;
            }
            MapTypeButton.Text = "...as " + MapTypeButton.Text;
        }

        private void RenderFromSave(object sender, RoutedEventArgs e)
        {
            worldPath = ((MenuItem)sender).Tag.ToString();
            worldName = ((MenuItem)sender).Header.ToString();
                
            RefreshImage(sender, e);
        }

        private void RenderFromPath(object sender, RoutedEventArgs e)
        {
            OpenFileDialog OafDialog = new OpenFileDialog();
            OafDialog.Filter = "Minecraft world|level.dat";
            OafDialog.FilterIndex = 1;
            OafDialog.CheckFileExists = true;
            OafDialog.CheckPathExists = true;
            if (OafDialog.ShowDialog() == true)
            {
                worldPath = Directory.GetParent(OafDialog.FileName).FullName;
                worldName = worldPath.Substring(worldPath.LastIndexOf('\\') + 1);
            }
            else return;

            RefreshImage(sender, e);
        }

        private void MCRegionCheck(string WorldPath)
        {
            if (Directory.Exists(Path.Combine(WorldPath, "region")))
            {   //World has MCRegion installed
                if (File.Exists("RegionTool.jar"))
                {
                    //Run MCRegion extraction tool
                    Process RegionExtractor = new Process();
                    RegionExtractor.StartInfo = new ProcessStartInfo("java", "-jar RegionTool.jar unpack \"" + WorldPath + "\"");

                    RegionExtractor.StartInfo.UseShellExecute = true;
                    RegionExtractor.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;

                    RenderInfoText.Text = "Running MCRegion converter...";
                    RegionExtractor.Start();
                    RegionExtractor.WaitForExit();
                }
                else
                {
                    MessageBox.Show("The world \"" + worldName + "\" appears to be using MCRegion, but the MCRegion support " +
                        "files are missing from your MTS installation. Please reinstall MTS to ensure the map is correct.",
                        "MCRegion support missing", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        private void RefreshImage(object sender, RoutedEventArgs e)
        {
            RenderSettings settings = new RenderSettings {
                WorldPath = worldPath,
                TileType = (ImageType)System.Enum.Parse(typeof(ImageType), Properties.Settings.Default.RenderMode),
                LightingLevel = Properties.Settings.Default.LightLevel,
                SpectralBlock = (byte)Properties.Settings.Default.spectrographBlock,
                Hell = false //HELL Setting - Make this read from an appropriate place once rendering Hell actually works
            };

            progressTextFormat = worldName + ": {0}";
            titleTextFormatRendered = String.Format(titleTextFormat, (worldName + " - {0}"));
            this.Title = String.Format(titleTextFormatRendered, "Rendering");

            this.RefreshMenu.Header = String.Format("Refresh {0}", worldName);

            SetMenuState(MenuMode.Rendering);
            try
            {
                StartRender(settings);
            }
            catch (Exception disaster)
            {
                MessageBox.Show("Failed to render " + worldName + ": " + disaster.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        [PreEmptive.Attributes.Feature("Render", EventType = PreEmptive.Attributes.FeatureEventTypes.Start)]
        private void StartRender(RenderSettings r)
        {
            renderWorker.RunWorkerAsync(r);
            RenderInfo.Visibility = Visibility.Visible;
        }

        private void ShowOptions(object sender, RoutedEventArgs e)
        {
            Options optionsWindow = new Options();
            optionsWindow.Owner = this;
            optionsWindow.ShowDialog();
            UpdateTypeButton();
        }

        private void ShowAbout(object sender, RoutedEventArgs e)
        {
            About aboutWindow = new About();
            aboutWindow.Owner = this;
            aboutWindow.ShowDialog();
        }

        private void MenuLink(object sender, RoutedEventArgs e)
        {
            MenuItem source = (MenuItem)sender;
            System.Diagnostics.Process.Start(source.Tag.ToString());
        }

        #region Output options

        [PreEmptive.Attributes.Feature("ViewImage")]
        private void OpenImage(object sender, RoutedEventArgs e)
        {
            if (imageTerrain.Source == null)
                return;

            BitmapSource image = (BitmapSource)imageTerrain.Source;
            string tempPath = Path.GetTempFileName().Replace(".tmp",".png");
            FileStream stream = new FileStream(tempPath, FileMode.Create);

            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(image));
            encoder.Save(stream);
            stream.Flush();
            stream.Close();

            Process.Start(tempPath);
        }

        [PreEmptive.Attributes.Feature("PostImage")]
        private void PostImage(object sender, RoutedEventArgs e)
        {
            if (imageTerrain.Source == null)
                return;

            SetMenuState(MenuMode.Rendering);
            RenderInfo.Visibility = System.Windows.Visibility.Visible;
            RenderInfoText.Text = "Uploading image...";

            BitmapSource image = (BitmapSource)imageTerrain.Source;
            string tempPath = Path.GetTempFileName().Replace(".tmp",".png");
            FileStream stream = new FileStream(tempPath, FileMode.Create);
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(image));
            encoder.Save(stream);
            stream.Flush();
            stream.Close();

            WebClient client = new WebClient();
            client.UploadFileCompleted += new UploadFileCompletedEventHandler(PostImageCompleted);
            client.UploadFileAsync(new Uri("http://mts.davejk.net/PostImage.ashx"), "POST", tempPath);
        }
        void PostImageCompleted(object sender, UploadFileCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                byte[] r = e.Result;
                UTF8Encoding enc = new UTF8Encoding();

                string response = enc.GetString(r);

                if (response.StartsWith("http://"))
                    Process.Start(response);
                else
                {
                    MessageBox.Show("Could not upload your image: " + response,
                        "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            else
            {
                MessageBox.Show("Could not upload your image: " + e.Error.InnerException.Message,
                    "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            RenderInfo.Visibility = Visibility.Hidden;
            SetMenuState(MenuMode.PostRender);
        }

        [PreEmptive.Attributes.Feature("CopyImage")]
        private void CopyImage(object sender, RoutedEventArgs e)
        {
            if (imageTerrain.Source == null)
                return;

            BitmapSource image = (BitmapSource)imageTerrain.Source;
            Clipboard.SetImage(image);
        }

        [PreEmptive.Attributes.Feature("CreateScript")]
        private void CreateScript(object sender, RoutedEventArgs e)
        {
            string command = "mts-cli ";
            switch ((ImageType)System.Enum.Parse(typeof(ImageType), Properties.Settings.Default.RenderMode))
            {
                case ImageType.TERRAIN:
                    command += "-t ";
                    break;
                case ImageType.OBLIQUE:
                    command += "-o " + Properties.Settings.Default.LightLevel + " ";
                    break;
                case ImageType.CAVEMAP:
                    command += "-c ";
                    break;
                case ImageType.HEIGHTMAP:
                    command += "-h ";
                    break;
                case ImageType.SPECTROGRAPH:
                    command += "-s " + Properties.Settings.Default.spectrographBlock + " ";
                    break;
            }
            command += "\"" + worldPath + "\"";

            Clipboard.SetText(command);

            MessageBox.Show("The MTS-CLI command for the current map has been copied to your clipboard.", "MTS-CLI command", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        [PreEmptive.Attributes.Feature("SaveImage")]
        private void SaveImage(object sender, RoutedEventArgs e)
        {
            if (imageTerrain.Source == null)
                return;

            BitmapSource image = (BitmapSource)imageTerrain.Source;

            SaveFileDialog saveDialog = new SaveFileDialog();
            saveDialog.Filter = "PNG image (*.png)|*.png|JPEG image (*.jpeg, *.jpg)|*.jpeg,*.jpg";
            saveDialog.FilterIndex = 1;
            saveDialog.RestoreDirectory = true;

            if (saveDialog.ShowDialog() == true)
            {
                FileStream stream = new FileStream(saveDialog.FileName, FileMode.Create);
                BitmapEncoder encoder;

                switch (saveDialog.FilterIndex)
                {
                    case 2: //jpeg
                        encoder = new JpegBitmapEncoder();
                        break;
                    default: //png
                        encoder = new PngBitmapEncoder();
                        break;
                }

                encoder.Frames.Add(BitmapFrame.Create(image));
                encoder.Save(stream);
                stream.Flush();
                stream.Close();
                Console.Out.WriteLine("File written to {0}", stream.Name);
            }
        }

        #endregion

        private enum MenuMode
        {
            NoRender,
            Rendering,
            PostRender
        }
        private void SetMenuState(MenuMode Mode)
        {
            RenderMenu.IsEnabled = (Mode != MenuMode.Rendering);
            OptionsMenu.IsEnabled = (Mode != MenuMode.Rendering);

            RefreshMenu.IsEnabled = (Mode == MenuMode.PostRender);
            SaveMenu.IsEnabled = (Mode == MenuMode.PostRender);
            ViewMenu.IsEnabled = (Mode == MenuMode.PostRender);

            int bottom = (scrollTerrain.ComputedHorizontalScrollBarVisibility == Visibility.Visible ? 
                (int)(scrollTerrain.ActualHeight - scrollTerrain.ViewportHeight) :
                0);
            int right = (scrollTerrain.ComputedVerticalScrollBarVisibility == Visibility.Visible ?
                (int)(scrollTerrain.ActualWidth - scrollTerrain.ViewportWidth) :
                0);
            RenderInfo.Margin = new Thickness(0, 0, right, bottom);
        }

        #region Renderer

        private void RendererWork(object sender, DoWorkEventArgs e)
        {
            this.startTime = DateTime.Now;
            BackgroundWorker worker = sender as BackgroundWorker;
            RenderSettings settings = e.Argument as RenderSettings;
            e.Result = renderer.renderWorld(settings, worker);
        }

        private void RendererProgress(object sender, ProgressChangedEventArgs e)
        {
            this.RenderInfoText.Text = String.Format(progressTextFormat, e.UserState.ToString() + 
                (e.ProgressPercentage > 0 
                    ? " - " + e.ProgressPercentage + "%"
                    : ""));
            TaskbarItemInfo.ProgressValue = (double)e.ProgressPercentage / 100;
        }

        [PreEmptive.Attributes.Feature("Render", EventType = PreEmptive.Attributes.FeatureEventTypes.Stop)]
        private void RendererComplete(object sender, RunWorkerCompletedEventArgs e)
        {
            RenderInfo.Visibility = Visibility.Collapsed;
            RenderInfoText.Text = "Looking for world data...";

            SetMenuState(MenuMode.PostRender);
            TaskbarItemInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
            if (e.Error != null)
            {
#if DEBUG
                throw e.Error;
#else
                string n = Environment.NewLine;
                try
                {
                    //Write a crash dump
                    TextWriter ErrorWriter = new StreamWriter("RenderFailure.txt");
                    AssemblyName AppAssembly = Assembly.GetExecutingAssembly().GetName();
                    ErrorWriter.WriteLine("MTS version:       " + AppAssembly.Version.ToString());
                    ErrorWriter.WriteLine("Failure occurred:  " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());

                    ErrorWriter.WriteLine("Operating system:  " + Environment.OSVersion.ToString() + (Environment.Is64BitOperatingSystem ? " (64-bit)" : ""));
                    ErrorWriter.WriteLine("CLR version:       " + Environment.Version.ToString());

                    ErrorWriter.WriteLine(n + "Please report what you did to cause this bug at http://minecraft.codeplex.com/WorkItem/Create, attaching this file to the report." + n);

                    ErrorWriter.WriteLine(e.Error.ToString());

                    ErrorWriter.Close();

                    if (e.Error.GetType() == typeof(DirectoryNotFoundException))
                    {
                        MessageBox.Show("Rendering failed because the selected world is in an outdated format.\n\nUpdate the game, load the world, then try again." +
                        "\n\nIf you want to render worlds from older versions of Minecraft, you may need to obtain an older version of MTS.",
                        "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    else
                    {
                        MessageBox.Show("Rendering failed: " + e.Error.Message + n + n +
                        "An error report has been saved as RenderFailure.txt in your MTS directory. Check this file for more information.",
                        "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Rendering failed: " + e.Error.Message + n + n +
                        "An error report could not be saved. Please ensure that MTS has write access to its directory.",
                        "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
                this.Title = String.Format(titleTextFormatRendered, "Rendering failed");
#endif
            }
            else if (e.Cancelled)
            {
                MessageBox.Show("Rendering was aborted.", "Render incomplete", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                this.Title = String.Format(titleTextFormatRendered, "Rendering cancelled");
            }
            else
            {
                BitmapSource output = (BitmapSource)e.Result;

                TimeSpan elapsed = DateTime.Now - startTime;
                this.UpdateRenderedImage(output);
                this.Title = String.Format(titleTextFormatRendered, String.Format("Rendered in {0:00}:{1:00}", Math.Floor(elapsed.TotalMinutes), elapsed.Seconds));
            }


        }

        private void UpdateRenderedImage(BitmapSource image)
        {
            this.imageTerrain.Source = image;
        }

        #endregion

        #region Scrolling image viewer

        private Point terrainScrollStartPoint;
        private Point terrainScrollStartOffset;
        private bool ScrollFromBar;

        private void scrollTerrain_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (
                !(e.GetPosition(this.scrollTerrain).X > scrollTerrain.ViewportWidth) &&
                !(e.GetPosition(scrollTerrain).Y > scrollTerrain.ViewportHeight)
               )
            {
                ScrollFromBar = false;
                if (this.scrollTerrain.IsMouseOver)
                {
                    terrainScrollStartPoint = e.GetPosition(this);
                    terrainScrollStartOffset.X = scrollTerrain.HorizontalOffset;
                    terrainScrollStartOffset.Y = scrollTerrain.VerticalOffset;

                    this.Cursor = (scrollTerrain.ExtentWidth > scrollTerrain.ViewportWidth) ||
                                    (scrollTerrain.ExtentHeight > scrollTerrain.ViewportHeight) ?
                                    Cursors.ScrollAll : Cursors.Arrow;
                }
            }
            else
            {
                ScrollFromBar = true;
            }
            base.OnPreviewMouseDown(e);
        }

        private void scrollTerrain_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (
                !ScrollFromBar
                )
            {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    Point point = e.GetPosition(this);

                    Point delta = new Point(
                        (point.X > this.terrainScrollStartPoint.X) ?
                            -(point.X - this.terrainScrollStartPoint.X) :
                            (this.terrainScrollStartPoint.X - point.X),

                        (point.Y > this.terrainScrollStartPoint.Y) ?
                            -(point.Y - this.terrainScrollStartPoint.Y) :
                            (this.terrainScrollStartPoint.Y - point.Y));

                    scrollTerrain.ScrollToHorizontalOffset(this.terrainScrollStartOffset.X + delta.X);
                    scrollTerrain.ScrollToVerticalOffset(this.terrainScrollStartOffset.Y + delta.Y);
                }
                else
                {
                    this.Cursor = Cursors.Arrow;
                }
                if (!this.IsMouseOver)
                {
                    this.Cursor = Cursors.Arrow;
                }
            }
            base.OnPreviewMouseMove(e);
        }

        private void scrollTerrain_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            this.Cursor = Cursors.Arrow;
            base.OnPreviewMouseUp(e);
        }

        #endregion

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
        }

        private void ToggleStats(object sender, RoutedEventArgs e)
        {
            bool Checked = ((MenuItem)sender).IsChecked;

            if(!Checked) { //Plead
                if(MessageBox.Show("Stat reporting sends bug and rendering information to the MTS team to help us improve the software you're using.\n\n" +
                    "The information is sent via PreEmptive Runtime Intelligence and CodePlex, and your computer cannot be uniquely identified.\n\n" +
                    "Are you sure you want to turn it off?",
                    "Turn off stats reporting", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No) {
                    ((MenuItem)sender).IsChecked = true;
                } else {
                    Properties.Settings.Default.ReportToRI = false;
                }
            } else {
                Properties.Settings.Default.ReportToRI = true;
            }

            Properties.Settings.Default.Save();
        }
    }
}
